package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.dao.SkuEsMapper;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SkuService;
import entity.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

@Service
public class SkuServiceImpl implements SkuService {

    @Autowired
    private SkuFeign skuFeign;

    @Autowired
    private SkuEsMapper skuEsMapper;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    /**
     * 导入sku数据到es
     */
    @Override
    public void importSku(){
        //调用changgou-service-goods微服务
        Result<List<Sku>> skuListResult = skuFeign.findAll();
        //将数据转成search.Sku
        List<SkuInfo> skuInfos=  JSON.parseArray(JSON.toJSONString(skuListResult.getData()),SkuInfo.class);
        for(SkuInfo skuInfo:skuInfos){
            Map<String, Object> specMap= JSON.parseObject(skuInfo.getSpec(),Map.class) ;
            skuInfo.setSpecMap(specMap);
        }
        skuEsMapper.saveAll(skuInfos);
    }

    /**
     * 多条件搜索
     * @param searchMap
     * @return
     */
    @Override
    public Map<String, Object> search(Map<String, String> searchMap) {
        //搜索条件封装
        NativeSearchQueryBuilder nativeSearchQueryBuilder = buildBasicQuery(searchMap);
        //集合搜索
        Map<String, Object> resultMap = searchList(nativeSearchQueryBuilder);
        //当用户选择了分类，并将分类作为搜索条件，则不需要对分类进行分组搜索，因为分组搜索的数据是用于显示分类搜索条件的
        //分类-》searchMap->category
       /* if(searchMap==null||StringUtils.isEmpty(searchMap.get("category"))){
            //分组查询分类集合
            List<String> categoryList = searchCategoryList(nativeSearchQueryBuilder);
            resultMap.put("categoryList",categoryList);
        }
        //当用户选择了品牌，并将品牌作为搜索条件，则不需要对品牌进行分组搜索，因为分组搜索的数据是用于显示品牌搜索条件的
        if(searchMap==null||StringUtils.isEmpty(searchMap.get("brand"))){
            //查询品牌集合
            List<String> brandList = searchBrandList(nativeSearchQueryBuilder);
            resultMap.put("brandList",brandList);
        }

        //规格查询
        Map<String, Set<String>> specList = searchSpecList(nativeSearchQueryBuilder);
        resultMap.put("specList",specList);*/

        //分组搜索实现
        Map<String, Object> groupMap = searchGroupList(nativeSearchQueryBuilder, searchMap);
        resultMap.putAll(groupMap);
        return resultMap;
    }
    //分组查询 ->分类分组、品牌分组、规格分组
    public Map<String,Object> searchGroupList(NativeSearchQueryBuilder nativeSearchQueryBuilder,Map<String,String> searchMap) {
        if(searchMap==null|| StringUtils.isEmpty(searchMap.get("category"))){
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName").size(100));
        }
        if(searchMap==null|| StringUtils.isEmpty(searchMap.get("brand"))){
            nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName").size(100));
        }
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(100));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);

        //定义一个Map存储所有分组结果
        Map<String,Object> groupMapResult=new HashMap<String, Object>();
        //获取分组数据
        if(searchMap==null|| StringUtils.isEmpty(searchMap.get("category"))){
            StringTerms categoryTerms = aggregatedPage.getAggregations().get("skuCategory");
            //获取分类分组集合数据
            List<String> categoryList = getGroupList(categoryTerms);
            groupMapResult.put("categoryList",categoryList);
        }
        if(searchMap==null|| StringUtils.isEmpty(searchMap.get("brand"))){
            StringTerms brandTerms = aggregatedPage.getAggregations().get("skuBrand");
            //获取品牌分组集合数据
            List<String> brandList = getGroupList(brandTerms);
            groupMapResult.put("brandList",brandList);
        }
        StringTerms specTerms = aggregatedPage.getAggregations().get("skuSpec");
        //获取规格分组集合数据->实现合并操作
        List<String> specList = getGroupList(specTerms);
        Map<String, Set<String>> specMap = putAllSpec(specList);
        groupMapResult.put("specList",specMap);
        return groupMapResult;
    }

    /**
     * 获取分组集合数据
     * @param stringTerms
     * @return
     */
    public List<String> getGroupList(StringTerms stringTerms){
        List<String > groupList=new ArrayList<String>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String fieldName = bucket.getKeyAsString();
            groupList.add(fieldName);
        }
        return groupList;
    }


    //搜索条件封装
    public NativeSearchQueryBuilder buildBasicQuery(Map<String, String> searchMap) {
        //NativeSearchQueryBuilder 搜索条件构建对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder=new NativeSearchQueryBuilder();

        //BoolQuery must,must_not,should
        BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery();

        if (searchMap !=null&& searchMap.size()>0){
            //根据关键词搜索
            String keywords = searchMap.get("keywords");
            //如果不为空，则搜索关键词数据
            if(!StringUtils.isEmpty(keywords)){
                //nativeSearchQueryBuilder.withQuery(QueryBuilders.queryStringQuery(keywords).field("name"));
                boolQueryBuilder.must(QueryBuilders.queryStringQuery(keywords).field("name"));
            }
            //输入了品牌
            if(!StringUtils.isEmpty(searchMap.get("brand"))){
                boolQueryBuilder.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
            }

            //输入了分类条件
            if(!StringUtils.isEmpty(searchMap.get("category"))){
                boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName",searchMap.get("category")));
            }

            //规格过滤实现
            for (Map.Entry<String, String> entry : searchMap.entrySet()) {
                String key = entry.getKey();
                //如果key以spec_开始，则表示规格筛选查询
                if(key.startsWith("spec_")){
                    String value= entry.getValue();
                    boolQueryBuilder.must(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword",value));
                }
            }

            //price 0-500元 ~~~~ 5000元以上
            String price=searchMap.get("price");
            if(!StringUtils.isEmpty(price)){
                //去掉中文 元、以上
                price=price.replace("元","").replace("以上","");
                //prices[]根据- 分割 [0,500] [500,1000] [3000]
                String[] prices=price.split("-");
                //x一定不为空，y有可能为空
                if(prices!=null&&prices.length>0){
                    //price>prices[0]
                    boolQueryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(prices[0])));
                    if(prices.length==2){
                        // price<=prices[1]
                        boolQueryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(prices[1])));
                    }
                }
            }

            //排序实现
            String sortField=searchMap.get("sortField");
            String sortRule=searchMap.get("sortRule");
            if(!StringUtils.isEmpty(sortRule)&&!StringUtils.isEmpty(sortField)){
                nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(sortRule.equals("DESC")? SortOrder.DESC:SortOrder.ASC));
            }



        }
        //分页，用户如果不传分页参数，则默认第一页
        Integer pageNum=coverterPage(searchMap);
        Integer size=30;
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum-1,size));

        //将boolQueryBuilder填充给nativeSearchQueryBuilder
        nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
        return nativeSearchQueryBuilder;
    }

    /**
     * 接受前端传入的分页参数
     * @param searchMap
     * @return
     */
    public Integer coverterPage(Map<String ,String >searchMap){
        if(searchMap!=null){
            String pageNum = searchMap.get("pageNum");
            try {
                return Integer.parseInt(pageNum);
            } catch (NumberFormatException e) {
            }
        }
        return 1;
    }


    //结果集搜索
    public Map<String, Object> searchList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        //高亮配置
        HighlightBuilder.Field field=new HighlightBuilder.Field("name");//指定高亮域
        //前缀<em style="color:red">
        field.preTags("<em style=\"color:red\">");
        //后缀</em>
        field.postTags("</em>");
        //碎片长度 关键词数据的长度
        field.fragmentSize(100);
        //添加高亮
        nativeSearchQueryBuilder.withHighlightFields(field);

        /**
         * 执行搜索，响应结果给我
         * 搜索条件封装对象
         * 搜索的结果集（集合数据）需要转换的类型
         *  AggregatedPage<SkuInfo>搜索结果集的封装
         */
        //AggregatedPage<SkuInfo> page = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);
        AggregatedPage<SkuInfo> page = elasticsearchTemplate
                .queryForPage(
                        nativeSearchQueryBuilder.build(),  //搜索条件封装
                        SkuInfo.class,                     //数据集合要转换的类型的字节码
                        //SearchResultMapper);             //执行搜索后，将数据结果集封装到该对象中
                        new SearchResultMapper(){
                            @Override
                            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                                //存储所有转换后的高亮数据对象
                                List<T> list=new ArrayList<T>();
                                //执行查询，获取所有数据->结果集[非高亮数据|高亮数据]
                                for (SearchHit hit : response.getHits()) {
                                    //分析结果集数据，获取非高亮数据
                                    SkuInfo skuInfo=JSON.parseObject(hit.getSourceAsString(),SkuInfo.class);
                                    //分析结果集数据，获取高亮数据->只有某个域的高亮数据
                                    HighlightField highlightField = hit.getHighlightFields().get("name");
                                    if (highlightField!=null&&highlightField.getFragments()!=null){
                                        //高亮数据读取处理
                                        Text[] fragments = highlightField.getFragments();
                                        StringBuffer buffer=new StringBuffer();
                                        for (Text fragment : fragments) {
                                            buffer.append(fragment.toString());
                                        }
                                        //非高亮数据中指定的域替换成高亮数据
                                        skuInfo.setName(buffer.toString());
                                    }
                                    list.add((T) skuInfo);
                                }
                                //将数据返回
                                return new AggregatedPageImpl<T>(list, pageable, response.getHits().getTotalHits());
                            }
                        });
        //获取搜索封装信息


        //分页参数-总记录数
        long totalElements = page.getTotalElements();
        //总页数
        long totalPages = page.getTotalPages();
        //分析数据
        List<SkuInfo> contents = page.getContent();
        //封装一个map存储所有数据，并返回
        Map<String ,Object> resultMap=new HashMap<String, Object>();
        resultMap.put("rows",contents);
        resultMap.put("total",totalElements);
        resultMap.put("totalPages",totalPages);


        NativeSearchQuery query = nativeSearchQueryBuilder.build();
        Pageable pageable = query.getPageable();
        int pageSize = pageable.getPageSize();
        int pageNumber = pageable.getPageNumber();

        resultMap.put("pageSize",pageSize);
        resultMap.put("pageNumber",pageNumber);

        return resultMap;
    }

    //分类分组查询
    public List<String> searchCategoryList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName").size(10000));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);

        //获取分组数据
        StringTerms stringTerms = aggregatedPage.getAggregations().get("skuCategory");
        List<String > categoryList=new ArrayList<String>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String categoryName = bucket.getKeyAsString();
            categoryList.add(categoryName);
        }
        return categoryList;
    }

    //品牌分组查询
    public List<String> searchBrandList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrand").field("brandName").size(10000));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);

        //获取分组数据
        StringTerms stringTerms = aggregatedPage.getAggregations().get("skuBrand");
        List<String > brandList=new ArrayList<String>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String  brandName= bucket.getKeyAsString();
            brandList.add(brandName);
        }
        return brandList;
    }

    //规格统计分组查询
    public Map<String,Set<String>> searchSpecList(NativeSearchQueryBuilder nativeSearchQueryBuilder) {
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(10000));
        AggregatedPage<SkuInfo> aggregatedPage = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class);

        //获取分组数据
        StringTerms stringTerms = aggregatedPage.getAggregations().get("skuSpec");

        List<String > specList=new ArrayList<String>();
        for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
            String specName = bucket.getKeyAsString();
            specList.add(specName);
        }

        Map<String, Set<String>> allSpec = putAllSpec(specList);

        return allSpec;
    }

    public Map<String, Set<String>> putAllSpec(List<String> specList) {
        //合并后的map对象
        Map<String, Set<String>> allSpec=new HashMap<String, Set<String>>();
        //1.循环specList
        for (String spec : specList) {
            //2.将每个Json字符串转成map
            Map<String ,String >specMap = JSON.parseObject(spec, Map.class);
            //3.将每个map对象合成成一个Map<String,Set<String>>
            //4.合并流程,循环所有map
            for (Map.Entry<String, String> entry : specMap.entrySet()) {
                //4.1取出当前map，并且获取对应的key，以及对应value
                String key = entry.getKey();
                String value = entry.getValue();
                //4.2将当前循环的数据河滨到一个Map<String,Set<String>>中
                //获取当前规格对应的Set集合数据
                Set<String > specSet=allSpec.get(key);
                if(specSet==null){
                    specSet=new HashSet<String>();
                }
                specSet.add(value);
                allSpec.put(key,specSet);
            }
        }
        return allSpec;
    }
}